package com.cug.campus_sh_market_server.util


import com.qcloud.cos.COSClient
import com.qcloud.cos.ClientConfig
import com.qcloud.cos.auth.BasicCOSCredentials
import com.qcloud.cos.exception.CosClientException
import com.qcloud.cos.exception.CosServiceException
import com.qcloud.cos.model.CannedAccessControlList
import com.qcloud.cos.model.CreateBucketRequest
import com.qcloud.cos.model.GetObjectRequest
import com.qcloud.cos.model.PutObjectRequest
import com.tencentcloudapi.common.profile.Region
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.io.File
import java.io.InputStream
import javax.annotation.PreDestroy


/**
 * @description COS对象(为适应场景, 以下都称"文件"而非"对象")存储工具类
 * 项目全局一份对象, 建议注入使用
 * @author sxt
 * @date 2021/3/23 3:52 下午
 * @version 1.0
 */
@Suppress("unused")
@Configuration
@ConfigurationProperties(prefix = "qcloud.cos")
class COSUtil {

    private val log = LoggerFactory.getLogger(COSUtil::class.java)

    /**
     * SECRET_ID
     */
    var secretId: String = ""

    /**
     * SECRET_KEY
     */
    var secretKey: String = ""

    /**
     * REGION
     */
    var region: Region = Region.Ashburn

    /**
     * 默认存储桶
     */
    var defaultBucket: String? = null

    /**
     * cos操作Client
     */
    lateinit var cosClient: COSClient

    /**
     * 注入COSClient对象
     * 注意哦: COSClient 是线程安全的类，允许多线程访问同一实例。
     * 因为实例内部维持了一个连接池，创建多个实例可能导致程序资源耗尽，因此保证生命周期内实例只有一个。
     * @return COSClient
     */
    @Bean
    fun addCOSClient(): COSClient {
        // 初始化用户身份信息（secretId, secretKey）
        val cosCredentials = BasicCOSCredentials(secretId, secretKey)
        // 指定地域
        val region = com.qcloud.cos.region.Region(region.value)
        // 创建Client配置
        val clientConfig = ClientConfig(region)
        // 创建Client
        log.info("======创建COS Client======")
        cosClient = COSClient(cosCredentials, clientConfig)
        return cosClient
    }

    fun createBucket(bucketName: String, access: CannedAccessControlList = CannedAccessControlList.Private) {
        val createBucketRequest = CreateBucketRequest(bucketName)
        // 默认设置 bucket 的权限为 Private(私有读写)、其他可选有 PublicRead（公有读私有写）、PublicReadWrite（公有读写）
        createBucketRequest.cannedAcl = access
        try {
            cosClient.createBucket(createBucketRequest)
        } catch (serverException: CosServiceException) {
            serverException.printStackTrace()
        } catch (clientException: CosClientException) {
            clientException.printStackTrace()
        }
    }

    /**
     * 查询所有的桶列表
     * @return List<Bucket> 桶列表
     */
    fun listBucket(): List<Bucket> {
        return cosClient.listBuckets()
            .asSequence()
            .map {
                Bucket(it.name, it.location)
            }.toList()
    }

    /**
     * 上传不超过5GB的文件
     * @param bucketName String? 桶名, 默认桶可在配置文件中指定
     * @param file File 需要上传的文件对象
     * @param folder String 目标文件夹路径, 默认default(最后不要加/)
     * @param fileName String? 文件名（不含 ".文件类型"）, 若不指定则为: 文件名本身-UUID
     * @return Triple<Boolean, String?, String?> first: 是否成功 second: 成功后文件全路径 third: 成功后文件的相对路径
     */
    fun putFile(
        bucketName: String? = defaultBucket,
        file: File,
        folder: String = "default",
        fileName: String? = null
    ): Triple<Boolean, String?, String?> {
        if (bucketName == null) {
            log.error("文件上传失败: 既没有指定上传文件的目标桶, 也没有配置默认桶, 请指定桶名")
            return Triple(false, null, null)
        }
        // 指定文件上传到 COS 上的路径，即对象键。例如对象键为folder/picture.jpg，则表示将文件 picture.jpg 上传到 folder 路径下
        val key =
            "$folder/${fileName?.let { "$fileName.${file.extension}" } ?: (file.nameWithoutExtension + "-" + UUIDUtils.getCode() + "." + file.extension)}"
        val putObjectRequest = PutObjectRequest(bucketName, key, file)
        return try {
            cosClient.putObject(putObjectRequest)
            log.info("文件上传成功: {}", key)
            Triple(true, "https://$bucketName.cos.${region.value}.myqcloud.com/$key", key)
        } catch (e: Exception) {
            log.error("文件上传失败: {}", e.message)
            Triple(false, null, null)
        }
    }

    /**
     * 获取指定文件流对象
     * 下载时调用
     * @param bucketName String? 桶名, 默认桶可在配置文件中指定
     * @param folder String 目标文件夹路径, 默认default/
     * @param fileName String 文件名, 必须指定
     * @return InputStream? 指定文件流对象
     */
    fun downLoadFile(
        bucketName: String? = defaultBucket,
        folder: String = "default",
        fileName: String
    ): InputStream? {
        // 指定文件在 COS 上的路径，即对象键。例如对象键为folder/picture.jpg，则表示下载的文件 picture.jpg 在 folder 路径下
        val key = "$folder/${fileName}"
        // 获取下载输入流
        val getObjectRequest = GetObjectRequest(bucketName, key)
        val cosObject = cosClient.getObject(getObjectRequest)
        return try {
            val objectInputStream = cosObject.objectContent
            log.info("文件流获取成功: {}", key)
            objectInputStream
        } catch (e: Exception) {
            log.error("文件流获取失败: {}", e.message)
            null
        }
        // 下载对象的 CRC64
//        val crc64Ecma = cosObject.objectMetadata.crc64Ecma
    }

    /**
     * 删除文件
     * 注意: COS在删除文件时无论目标文件是否存在都会执行删除操作
     * 因此在配置正确且指定文件名的情况下, 一般不会出现删除失败的情况
     * @param bucketName String? 桶名, 默认桶可在配置文件中指定
     * @param folder String 目标文件夹路径, 默认default/
     * @param fileName String 文件名, 必须指定
     * @return Boolean 是否成功
     */
    fun deleteFile(
        bucketName: String? = defaultBucket,
        folder: String = "default",
        fileName: String
    ): Boolean {
        // 指定被删除的文件在 COS 上的路径，即对象键。例如对象键为folder/picture.jpg，则表示删除位于 folder 路径下的文件 picture.jpg
        val key = "$folder/${fileName}"
        return deleteFile(bucketName, key)
    }

    /**
     * 通过文件相对路径来删除文件
     * 注意: COS在删除文件时无论目标文件是否存在都会执行删除操作
     * 因此在配置正确且指定文件路径的情况下, 一般不会出现删除失败的情况
     * @param bucketName String? 桶名, 默认桶可在配置文件中指定
     * @param filePath String 文件路径, 必须指定
     * @return Boolean 是否成功
     */
    fun deleteFile(
        bucketName: String? = defaultBucket,
        filePath: String
    ): Boolean {
        if (bucketName == null) {
            log.error("文件删除失败: 既没有指定文件所在的目标桶, 也没有配置默认桶, 请指定桶名")
            return false
        }
        return try {
            cosClient.deleteObject(bucketName, filePath)
            log.info("文件删除成功: {}", filePath)
            true
        } catch (e: Exception) {
            log.error("文件删除失败: {}", e.message)
            false
        }
    }

    /**
     * 桶
     */
    data class Bucket(
        /**
         * 桶名
         */
        val bucketName: String,

        /**
         * 桶所在区域
         */
        val bucketLocation: String


    ) {
        override fun toString(): String {
            return "Bucket(bucketName='$bucketName', bucketLocation='$bucketLocation')"
        }
    }

    @PreDestroy
    private fun shutDownCOS() {
        log.info("======关闭COS Client======")
        cosClient.shutdown()
    }
}
